<!DOCTYPE html>
<html>
<head>
    <title>ideal</title>
    <meta charset="utf-8">
</head>
<body>
<div id="test" sd-on-click="changeMessage">
    <p sd-text="msg | capitalize" sd-show="something">1</p>
    <p sd-show="something">2</p>
    <p sd-text="hello">3</p>
</div>
<script>
    var prefix = 'sd';//前缀
    //定义要用到的所有指令
    var Directives = {
        text: function (el, value) {
            el.textContent = value;
        },
        show: function (el, value) {
            el.style.display = value ? '' : 'node'
        },
        on: {
            update:function(el,handler,event,directive){
                if(!directive.handlers){
                    directive.handlers = {}
                }

                var handlers = directive.handlers;
                if(handlers[event]){
                    el.removeEventListener(event,handlers[event]);
                }
                if(handler){
                    handler = handler.bind(el);
                    el.addEventListener(event,handler);
                    handlers[event] = handler;
                }
            },
            unbind:function(el,event,directive){
                el.removeEventListener(event,directive.handlers[event])
            },
            customFilter:function(handler,selectors){
                return function(e){
                    var match = selectors.every(function(selector){
                        return e.target.webkitMatchesSelector(selector);
                    });
                    if(match) handler.apply(this,arguments);
                };
            }
        }
    };
    //定义过滤器
    var Filters = {
        capitalize:function(value){
            value = value.toString();
            return value.charAt(0).toUpperCase()+value.slice(1);
        }
    };
    //找到dom中所有含有指令的节点选择器
    var selector = Object.keys(Directives).map(function (directive) {
        return '[' + prefix + '-' + directive + ']';
    }).join(',');

    function Seed(opts) {
        var self = this;
        var root = this.el = document.getElementById(opts.id);
        var els = root.querySelectorAll(selector);
        var bindings=self.bindings = {};//内部的数据
        self.scope = {};//外部的数据

        //对所有节点进行解析
        [].forEach.call(els, processNode);
        //对root节点解析
        processNode(root);

        //对数据初始化
        Object.keys(opts.scope).forEach(function(key){
            self.scope[key]=opts.scope[key];
        });


        function processNode(el) {
            //格式化attrs数据
            [].map.call(el.attributes, function (attr) {
                return {
                    name: attr.name,
                    value: attr.value
                }
            }).forEach(function (attr) {
                var directive = parseDirective(attr);
                //将指令绑定到元素并存入bindings
                if(directive){
                   bindDirective(self,el,bindings,directive);
                }
            })
        }

        //{name:'sd-text',value:'msg'} {name: "sd-on-click", value: "changeMessage"}
        function parseDirective(attr) {
            //如果没有特定前缀退出
            if (attr.name.indexOf(prefix) === -1) return;

            var noprefix = attr.name.slice(prefix.length + 1);
            var argIndex = noprefix.indexOf('-');
            var dirname = argIndex === -1
                ? noprefix
                : noprefix.slice(0,argIndex);
            var def = Directives[dirname];
            var arg = argIndex === -1
                ? null
                : noprefix.slice(argIndex+1);

            var exp = attr.value;
            var pipeIndex = exp.indexOf('|');
            var key = pipeIndex === -1
                ? exp.trim()
                : exp.slice(0,pipeIndex).trim();
            var filters = pipeIndex === -1
                ? null
                : exp.slice(pipeIndex+1).split('|').map(function(filters){
                    return filters.trim();
                });
            return def
                ? {
                    attr:attr,
                    key:key,
                    filters:filters,
                    definition:def,
                    argument:arg,
                    update: typeof def === 'function'
                        ? def
                        : def.update
                }
                :null
        }

        function bindDirective (seed,el,bindings,directive) {
            var key = directive.key;
            var binding = bindings[key];
            if(!binding){
                bindings[key] = binding = {
                    value: undefined,
                    directives:[]
                }
            }
            directive.el = el;
            binding.directives.push(directive);
            if(directive.bind){
                directive.bind(el,binding.value)
            }
            if(!seed.scope.hasOwnProperty(key)){
                bindAccessors(seed,key,binding);
            }
        }

        function bindAccessors(seed,key,binding){
            Object.defineProperty(seed.scope,key,{
                get:function(){
                    return binding.value
                },
                set:function(value){
                    binding.value = value;
                    binding.directives.forEach(function(directive){
                        if(value && directive.filters){
                            value = applyFilters(value,directive);
                        }
                        directive.update(
                            directive.el,
                            value,
                            directive.argument,
                            directive,
                            seed
                        )
                    })
                }
            })
        }

        function applyFilters(value,directive){
            if(directive.definition.customFilter){
                return directive.definition.customFilter(value,directive.filters);
            }else{
                directive.filters.forEach(function(filter){
                    if(Filters[filter]){
                        value = Filters[filter](value);
                    }
                });
                return value;
            }
        }
    }

    var app = new Seed({
        id: 'test',
        scope: {
            msg: 'hello',
            hello: 'hello world!',
            changeMessage: function () {
                app.scope.msg = 'change';
            }
        }
    })

</script>
</body>
</html>